"""MQTT 客户端服务封装。"""

from __future__ import annotations

import json
import logging
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from typing import Optional

import paho.mqtt.client as mqtt
from PySide6.QtCore import QObject, Signal

from src.core.audit import audit_logger


logger = logging.getLogger(__name__)


class MQTTConnectionError(RuntimeError):
    """包装连接错误，携带错误码信息。"""

    def __init__(self, rc: int, description: str) -> None:
        super().__init__(description)
        self.rc = rc


@dataclass
class MQTTConfig:
    host: str
    port: int = 1883
    username: Optional[str] = None
    password: Optional[str] = None


class MQTTService(QObject):
    """对 paho-mqtt 进行轻量封装，便于在 UI 中调用。"""

    connecting = Signal()
    connected = Signal()
    disconnected = Signal(int)
    connection_failed = Signal(int, str)
    # 异步发布相关的 Signal
    publish_started = Signal(str, dict)  # topic, payload
    publish_succeeded = Signal(str, dict, str)  # topic, payload, message_id
    publish_failed = Signal(str, dict, str)  # topic, payload, error_message

    def __init__(self) -> None:
        super().__init__()
        self._config: Optional[MQTTConfig] = None
        self._client: Optional[mqtt.Client] = None
        self._lock = threading.RLock()
        self._connect_thread: Optional[threading.Thread] = None
        # 使用线程池执行异步发布任务
        self._executor: Optional[ThreadPoolExecutor] = None
        self._max_workers = 5  # 最大并发发布任务数

    def configure(self, config: MQTTConfig) -> None:
        with self._lock:
            # 记录配置变更
            if self._config:
                audit_logger.log_config_change(
                    "mqtt_config",
                    old_value=f"{self._config.host}:{self._config.port}",
                    new_value=f"{config.host}:{config.port}",
                )
            self._config = config
            self._reset_client()
            self._connect_thread = None

    def connect(self, timeout: float = 5.0) -> None:
        """异步建立连接，避免阻塞 UI 线程。"""
        with self._lock:
            if not self._config:
                raise RuntimeError("在发布消息前必须先调用 configure 设置 MQTT 参数")

            client = self._ensure_client(auto_connect=False)
            if client.is_connected():
                return

            if self._connect_thread and self._connect_thread.is_alive():
                logger.debug("已有正在进行的 MQTT 连接任务，忽略重复请求")
                return

            thread = threading.Thread(
                target=self._run_connect,
                name="MQTTConnectWorker",
                args=(timeout,),
                daemon=True,
            )
            self._connect_thread = thread

        self.connecting.emit()
        thread.start()

    def publish(self, topic: str, payload: dict, qos: int = 0) -> None:
        """同步发布 MQTT 消息（保留向后兼容）。"""
        with self._lock:
            client = self._ensure_client()
            message = json.dumps(payload, ensure_ascii=False)
            logger.debug("发布 MQTT 消息：topic=%s payload=%s", topic, message)
        
        # 在锁外等待连接建立，避免长时间持有锁
        # 如果客户端未连接，等待连接建立（最多等待10秒）
        if not client.is_connected():
            logger.debug("MQTT 客户端未连接，等待连接建立...")
            deadline = time.monotonic() + 10.0
            while time.monotonic() < deadline:
                if client.is_connected():
                    logger.debug("MQTT 连接已建立，可以发布消息")
                    break
                time.sleep(0.1)
            
            # 再次检查连接状态
            if not client.is_connected():
                raise RuntimeError("MQTT 客户端未连接，无法发布消息")
        
        result = client.publish(topic, message, qos=qos)
        result.wait_for_publish(timeout=5)
        if result.rc != mqtt.MQTT_ERR_SUCCESS:
            raise RuntimeError(f"MQTT 消息发布失败，错误码：{result.rc}")

    def publish_async(self, topic: str, payload: dict, qos: int = 0) -> None:
        """异步发布 MQTT 消息，不阻塞调用线程。
        
        发布结果通过 Signal 通知：
        - publish_started: 发布开始时触发
        - publish_succeeded: 发布成功时触发，携带 topic、payload 和 message_id
        - publish_failed: 发布失败时触发，携带 topic、payload 和错误信息
        """
        # 确保线程池已初始化
        if self._executor is None:
            self._executor = ThreadPoolExecutor(
                max_workers=self._max_workers,
                thread_name_prefix="MQTTPublishWorker"
            )
        
        # 发送开始信号
        self.publish_started.emit(topic, payload)
        
        # 在线程池中执行发布任务
        future = self._executor.submit(self._publish_in_thread, topic, payload, qos)
        
        # 添加回调处理结果（在后台线程中执行）
        def handle_result(fut):
            try:
                message_id = fut.result()
                # 发布成功，发送成功信号
                self.publish_succeeded.emit(topic, payload, message_id)
            except Exception as exc:  # noqa: BLE001
                # 发布失败，发送失败信号
                error_msg = str(exc)
                logger.error("异步发布 MQTT 消息失败：topic=%s, error=%s", topic, error_msg)
                self.publish_failed.emit(topic, payload, error_msg)
        
        future.add_done_callback(handle_result)

    def _publish_in_thread(self, topic: str, payload: dict, qos: int = 0) -> str:
        """在线程中执行实际的发布操作，返回消息 ID。"""
        with self._lock:
            if not self._config:
                raise RuntimeError("在发布消息前必须先调用 configure 设置 MQTT 参数")
            
            client = self._ensure_client()
            message = json.dumps(payload, ensure_ascii=False)
            logger.debug("异步发布 MQTT 消息：topic=%s payload=%s", topic, message)
            
            # 提取消息 ID（如果存在）
            message_id = payload.get("msgid", "")
        
        # 在锁外等待连接建立，避免长时间持有锁
        # 如果客户端未连接，等待连接建立（最多等待10秒）
        if not client.is_connected():
            logger.debug("MQTT 客户端未连接，等待连接建立...")
            deadline = time.monotonic() + 10.0
            while time.monotonic() < deadline:
                if client.is_connected():
                    logger.debug("MQTT 连接已建立，可以发布消息")
                    break
                time.sleep(0.1)
            
            # 再次检查连接状态
            if not client.is_connected():
                error_msg = "MQTT 客户端未连接，无法发布消息"
                logger.error(error_msg)
                # 记录审计日志
                audit_logger.log_mqtt_publish(
                    topic,
                    payload,
                    result="failed",
                    error_message=error_msg,
                )
                raise RuntimeError(error_msg)
        
        try:
            result = client.publish(topic, message, qos=qos)
            result.wait_for_publish(timeout=5)
            
            if result.rc != mqtt.MQTT_ERR_SUCCESS:
                error_msg = f"MQTT 消息发布失败，错误码：{result.rc}"
                logger.error(error_msg)
                # 记录审计日志
                audit_logger.log_mqtt_publish(
                    topic,
                    payload,
                    result="failed",
                    error_message=error_msg,
                )
                raise RuntimeError(error_msg)
            
            logger.info("异步发布 MQTT 消息成功：topic=%s, msgid=%s", topic, message_id)
            # 记录审计日志
            audit_logger.log_mqtt_publish(topic, payload, result="success")
            return message_id
        except Exception as exc:  # noqa: BLE001
            logger.exception("发布 MQTT 消息时出现异常")
            # 记录审计日志
            audit_logger.log_mqtt_publish(
                topic,
                payload,
                result="error",
                error_message=str(exc),
            )
            raise

    def is_connected(self) -> bool:
        return bool(self._client and self._client.is_connected())

    def _run_connect(self, timeout: float) -> None:
        try:
            self._connect()
            if timeout > 0:
                deadline = time.monotonic() + timeout
                while time.monotonic() < deadline:
                    if self.is_connected():
                        break
                    time.sleep(0.1)

                if not self.is_connected():
                    logger.error("等待 MQTT 连接确认超时")
                    error_msg = "等待 MQTT 连接确认超时"
                    # 记录审计日志
                    if self._config:
                        audit_logger.log_mqtt_connect(
                            self._config.host,
                            self._config.port,
                            username=self._config.username,
                            result="failed",
                            error_message=error_msg,
                        )
                    with self._lock:
                        self._reset_client()
                    self.connection_failed.emit(mqtt.MQTT_ERR_NO_CONN, error_msg)
        except MQTTConnectionError as exc:
            logger.error("MQTT 连接失败：%s", exc)
            # 记录审计日志
            if self._config:
                audit_logger.log_mqtt_connect(
                    self._config.host,
                    self._config.port,
                    username=self._config.username,
                    result="error",
                    error_message=str(exc),
                )
            with self._lock:
                self._reset_client()
            self.connection_failed.emit(exc.rc, str(exc))
        except Exception as exc:  # noqa: BLE001
            logger.exception("MQTT 连接过程中出现未知异常")
            # 记录审计日志
            if self._config:
                audit_logger.log_mqtt_connect(
                    self._config.host,
                    self._config.port,
                    username=self._config.username,
                    result="error",
                    error_message=str(exc),
                )
            with self._lock:
                self._reset_client()
            self.connection_failed.emit(mqtt.MQTT_ERR_NO_CONN, str(exc))
        finally:
            with self._lock:
                self._connect_thread = None

    def _ensure_client(self, *, auto_connect: bool = True) -> mqtt.Client:
        if not self._client:
            self._reset_client()

        assert self._client is not None

        if auto_connect and not self._client.is_connected():
            self._connect()

        return self._client

    def _reset_client(self) -> None:
        if self._client:
            try:
                self._client.loop_stop()
                self._client.disconnect()
            except Exception:  # noqa: BLE001
                logger.exception("断开 MQTT 客户端时出现异常")

        self._client = mqtt.Client()
        self._client.on_connect = self._on_connect
        self._client.on_disconnect = self._on_disconnect
        if self._config and self._config.username:
            self._client.username_pw_set(self._config.username, self._config.password or "")

    def _connect(self) -> None:
        with self._lock:
            if not self._config:
                raise MQTTConnectionError(mqtt.MQTT_ERR_NO_CONN, "在发布消息前必须先调用 configure 设置 MQTT 参数")

            assert self._client is not None
            logger.info("连接 MQTT：%s:%s", self._config.host, self._config.port)
            try:
                result = self._client.connect(self._config.host, self._config.port, keepalive=60)
            except Exception as exc:  # noqa: BLE001
                raise MQTTConnectionError(mqtt.MQTT_ERR_NO_CONN, f"MQTT 连接建立失败：{exc}") from exc

            if result != mqtt.MQTT_ERR_SUCCESS:
                error_text = mqtt.error_string(result)
                raise MQTTConnectionError(result, f"MQTT 连接失败，错误码：{result}，原因：{error_text}")

            self._client.loop_start()

    def _on_connect(self, client: mqtt.Client, userdata, flags, rc):  # type: ignore[override]
        if rc == 0:
            logger.info("MQTT 连接成功")
            # 记录审计日志
            if self._config:
                audit_logger.log_mqtt_connect(
                    self._config.host,
                    self._config.port,
                    username=self._config.username,
                    result="success",
                )
            self.connected.emit()
        else:
            logger.error("MQTT 连接失败，错误码：%s", rc)
            error_msg = mqtt.error_string(rc)
            # 记录审计日志
            if self._config:
                audit_logger.log_mqtt_connect(
                    self._config.host,
                    self._config.port,
                    username=self._config.username,
                    result="failed",
                    error_message=f"错误码: {rc}, {error_msg}",
                )
            self.connection_failed.emit(rc, error_msg)

    def _on_disconnect(self, client: mqtt.Client, userdata, rc):  # type: ignore[override]
        if rc == 0:
            logger.info("MQTT 已断开")
            audit_logger.log_mqtt_disconnect(reason="正常断开")
        else:
            logger.warning("MQTT 异常断开，错误码：%s", rc)
            audit_logger.log_mqtt_disconnect(
                reason="异常断开",
                error_code=rc,
            )
        self.disconnected.emit(rc)

    def shutdown(self) -> None:
        """关闭线程池，释放资源。"""
        if self._executor:
            logger.info("正在关闭 MQTT 发布线程池...")
            self._executor.shutdown(wait=True, timeout=5)
            self._executor = None


